import java.io.*;
import java.util.ArrayList;
import java.util.List;

enum AsmOp
{
    leaq,
    subq,
    pushq,
    popq,
    movq,
    addq,
    andq,
    salq,
    sarq,
    cmpq,
    testq,
    cmoveq,
    cmovneq,
    cmovlq,
    cmovleq,
    cmovgq,
    cmovgeq,
    jnzq,
    jzq,
    callq,
    jmpq,
    orq,
    xorq,
    notq,
    leave,
    ret,
    divq,
    mulq
}

class ValueContext
{
    ValueIr.ValueType type;
    Variable.VType vtype;
    BlockIr blockIr;
    FuncIr funcIr;
    int number;
}


public class AsmGenerator
{
    List<PackageIr> packageIrs;
    IrGenerator irGenerator;
    PackageIr currentPackage;
    public final String Rax = "%rax";
    public final String Rbx = "%rbx";
    public final String Rcx = "%rcx";
    public final String Rdx = "%rdx";
    public final String Rsi = "%rsi";
    public final String Rdi = "%rdi";
    public final String Rbp = "%rbp";
    public final String Rsp = "%rsp";
    public final String Rip = "%rip";
    QuadIr beforeQuad;

    AsmGenerator()
    {
        packageIrs = new ArrayList<>();
    }

    AsmGenerator(IrGenerator irGenerator)
    {
        this.irGenerator = irGenerator;
        packageIrs = irGenerator.packageIrs;
    }

    public void printAsm() throws IOException
    {
        FileOutputStream fileOutputStream = new FileOutputStream("asm.txt");
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "utf-8");
        println(outputStreamWriter, 0, ".data");
        for(PackageIr packageIr : packageIrs)
        {
            printPackageData(packageIr, outputStreamWriter);
        }
        println(outputStreamWriter, 0, ".text");
        for(PackageIr packageIr : packageIrs)
        {
            printPackageText(packageIr, outputStreamWriter);
        }
        outputStreamWriter.flush();
        outputStreamWriter.close();
        fileOutputStream.close();
    }

    public void printPackageText(PackageIr packageIr, OutputStreamWriter writer) throws IOException
    {
        currentPackage = packageIr;
        for(FuncIr funcIr : packageIr.funcIrs)
        {
            println(writer, 0, currentPackage.name + "." + funcIr.func.name + ":");
            printFuncLocalVar(funcIr.func, writer);
            BlockIr blockIr = funcIr.block;
            while(blockIr != null)
            {
                QuadIr quadIr = blockIr.first;
                println(writer, 1, "block" + blockIr.blockId + ":");
                while(quadIr != null)
                {
                    printQuad(quadIr, writer);
                    quadIr = quadIr.next;
                }
                blockIr = blockIr.next;
            }
        }
    }

    public void printQuad(QuadIr quadIr, OutputStreamWriter writer) throws IOException
    {
        switch(quadIr.op)
        {
            case AddIr: case SubIr: case AndIr: case ShlIr:
            case ShrIr: case OrIr: case XorIr: case NotIr:
            case MulIr: case DivIr: case RemIr:
                ValueContext ctx1 = new ValueContext();
                ValueContext ctx2 = new ValueContext();
                getVariable(quadIr.arg1, ctx1, Rax, writer);
                getVariable(quadIr.arg2, ctx2, Rbx, writer);
//                printCode(AsmOp.popq, Rbx, "", writer);
//                printCode(AsmOp.popq, Rax, "", writer);
//                if(ctx2.vtype == Variable.VType.Var || ctx2.vtype == Variable.VType.Const)
//                    printCode(AsmOp.movq, "0" + "(" + Rbx + ")", Rbx, writer);
                if(ctx1.vtype == Variable.VType.Var || ctx1.vtype == Variable.VType.Const || ctx1.vtype == null)
                    printCode(AsmOp.movq, "0" + "(" + Rax + ")", Rax, writer);
                switch(quadIr.op)
                {
                    case AddIr: printCode(AsmOp.addq, opTmp(Rbx, ctx2), Rax, writer);break;
                    case SubIr: printCode(AsmOp.subq, opTmp(Rbx, ctx2), Rax, writer);break;
                    case AndIr: printCode(AsmOp.andq, opTmp(Rbx, ctx2), Rax, writer);break;
                    case ShlIr: printCode(AsmOp.salq, opTmp(Rbx, ctx2), Rax, writer);break;
                    case ShrIr: printCode(AsmOp.sarq, opTmp(Rbx, ctx2), Rax, writer);break;
                    case OrIr: printCode(AsmOp.orq, opTmp(Rbx, ctx2), Rax, writer);break;
                    case XorIr: printCode(AsmOp.xorq, opTmp(Rbx, ctx2), Rax, writer);break;
                    case NotIr: printCode(AsmOp.notq, opTmp(Rbx, ctx2), Rax, writer);break;
                    case MulIr:
//                        printCode(AsmOp.movq, opTmp(Rax, ctx1), Rax, writer);
                        printCode(AsmOp.mulq, opTmp(Rbx, ctx2), "", writer);
                        break;
                    case DivIr:
//                        printCode(AsmOp.movq, opTmp(Rax, ctx1), Rax, writer);
                        printCode(AsmOp.divq, opTmp(Rbx, ctx2), "", writer);
                        break;
                    case RemIr:
//                        printCode(AsmOp.movq, opTmp(Rax, ctx1), Rax, writer);
                        printCode(AsmOp.divq, opTmp(Rbx, ctx2), "", writer);
                        printCode(AsmOp.movq, Rbx, Rax, writer);
                        break;
                }
                printCode(AsmOp.pushq, Rax, "", writer);
                ValueContext resCtx = new ValueContext();
                printValue(quadIr.res, resCtx, writer);
                if(resCtx.vtype == Variable.VType.Var || resCtx.vtype == Variable.VType.Const)
                {
                    printCode(AsmOp.popq, Rbx, "", writer);
                    printCode(AsmOp.popq, addr(Rbx, 0), "", writer);
                }
                break;
            case AndNotIr:break;
            case EqlIR: case NeqIr: case LssIr: case LeqIR: case GtrIr:
            case GeqIr: case AndAndIr: case OrOrIr:
                ctx1 = new ValueContext();
                ctx2 = new ValueContext();
                printValue(quadIr.arg1, ctx1, writer);
                printValue(quadIr.arg2, ctx2, writer);
                printCode(AsmOp.popq, Rbx, "", writer);
                printCode(AsmOp.popq, Rax, "", writer);
                if(ctx1.vtype == Variable.VType.Var || ctx1.vtype == Variable.VType.Const)
                    printCode(AsmOp.movq, addr(Rbx, 0), Rbx, writer);
                printCode(AsmOp.cmpq, Rbx, Rax, writer);
                printCode(AsmOp.pushq, "$0", "", writer);
                switch(quadIr.op)
                {
                    case EqlIR:printCode(AsmOp.cmoveq, "$1", addr(Rsp, 0), writer);break;
                    case NeqIr:printCode(AsmOp.cmovneq, "$1", addr(Rsp, 0), writer);break;
                    case LssIr:printCode(AsmOp.cmovlq, "$1", addr(Rsp, 0), writer);break;
                    case LeqIR:printCode(AsmOp.cmovleq, "$1", addr(Rsp, 0), writer);break;
                    case GtrIr:printCode(AsmOp.cmovgq, "$1", addr(Rsp, 0), writer);break;
                    case GeqIr:printCode(AsmOp.cmovgeq, "$1", addr(Rsp, 0), writer);break;
                    case AndAndIr:
                        printCode(AsmOp.testq, Rbx,Rax, writer);
                        printCode(AsmOp.cmovneq, "$1", addr(Rsp, 0), writer);
                        break;
                    case OrOrIr:
                        printCode(AsmOp.addq, Rbx,Rax, writer);
                        printCode(AsmOp.cmovneq, "$1", addr(Rsp, 0), writer);
                        break;
                }
                break;
            case ParamIr:
                ValueContext valueContext = new ValueContext();
                printValue(quadIr.arg1, valueContext, writer);
                printCode(AsmOp.popq, Rax, "", writer);
                if(valueContext.vtype == Variable.VType.Var || valueContext.vtype == Variable.VType.Const)
                    printCode(AsmOp.movq, addr(Rax, 0), Rax, writer);
                printCode(AsmOp.pushq, Rax, "", writer);
                break;
            case AllocRetIr:
                valueContext = new ValueContext();
                printValue(quadIr.arg1, valueContext, writer);
                FuncIr funcIr = valueContext.funcIr;
                int retSize = 0;
                for(Variable variable : funcIr.func.results)
                    retSize += irGenerator.getSizeOfType((Expr) variable.type);
                if(retSize != 0)
                    printCode(AsmOp.subq, "$" + retSize, Rsp, writer);
                break;
            case CallIr:
                valueContext = new ValueContext();
                printValue(quadIr.arg1, valueContext, writer);
                funcIr = valueContext.funcIr;
                int paramSize = 0;
                for(Variable variable : funcIr.func.params)
                    paramSize += irGenerator.getSizeOfType((Expr)variable.type);
                printCode(AsmOp.callq, funcIr.belongPackage.name + "." + funcIr.func.name, "", writer);
                printCode(AsmOp.addq, Rsp, "$" + paramSize, writer);
                break;
            case GetCallIr:
                ctx1 = new ValueContext();
                ctx2 = new ValueContext();
                resCtx = new ValueContext();
                printValue(quadIr.arg1, ctx1, writer);
                printValue(quadIr.arg2, ctx2, writer);
                int number = ctx2.number;
                funcIr = ctx1.funcIr;
                Variable retVar = funcIr.func.results.get(number);
                paramSize = 0;
                for(Variable variable : funcIr.func.params)
                    paramSize += irGenerator.getSizeOfType((Expr)variable.type);
                paramSize += 16;
                printCode(AsmOp.leaq, addr(Rsp, retVar.offset-paramSize), Rax, writer);
                printCode(AsmOp.pushq, addr(Rax, 0), "", writer);
                Variable res = (Variable) quadIr.res.value;
                if(res.type != Variable.VType.Tmp)
                {
                    printValue(quadIr.res, resCtx, writer);
                    printCode(AsmOp.popq, Rbx, "", writer);
                    printCode(AsmOp.popq, Rax, "", writer);
                    printCode(AsmOp.movq, Rax, addr(Rbx, 0), writer);
                }
                break;
            case RetIr:
                ctx1 = new ValueContext();
                ctx2 = new ValueContext();
                printValue(quadIr.arg1, ctx1, writer);
                printCode(AsmOp.popq, Rbx, "", writer);
                if(ctx1.vtype == Variable.VType.Var || ctx1.vtype == Variable.VType.Const)
                    printCode(AsmOp.movq, addr(Rbx, 0), Rbx, writer);
                printValue(quadIr.res, ctx2, writer);
                printCode(AsmOp.popq, Rax, "", writer);
                printCode(AsmOp.movq, Rbx, addr(Rax, 0), writer);
                break;
            case AssignIr:
                ctx1 = new ValueContext();
                ctx2 = new ValueContext();
                printValue(quadIr.arg1, ctx1, writer);
                printValue(quadIr.res, ctx2, writer);
                printCode(AsmOp.popq, Rbx, "", writer);
                printCode(AsmOp.popq, Rax, "", writer);
                printCode(AsmOp.movq, opTmp(Rbx, ctx1), addr(Rax, 0), writer);
                break;
            case IfTrueIr:case IfFalseIr:
                ctx1 = new ValueContext();
                ctx2 = new ValueContext();
                printValue(quadIr.arg1, ctx1, writer);
                printCode(AsmOp.popq, Rax, "", writer);
                if(ctx1.vtype != Variable.VType.Tmp && ctx1.vtype != Variable.VType.Lit)
                    printCode(AsmOp.movq, addr(Rax, 0), Rax, writer);
                printCode(AsmOp.testq, Rax, Rax, writer);
                printValue(quadIr.arg2, ctx2, writer);
                if(quadIr.op == OPIr.IfTrueIr)
                    printCode(AsmOp.jnzq, "block"+ctx2.blockIr.blockId, "", writer);
                else
                    printCode(AsmOp.jzq, "block"+ctx2.blockIr.blockId, "", writer);
                break;
            case IndexIr:
                ctx1 = new ValueContext();
                ctx2 = new ValueContext();
                printValue(quadIr.arg1, ctx1, writer);
                printValue(quadIr.arg2, ctx2, writer);
                printCode(AsmOp.popq, Rbx, "", writer);
                printCode(AsmOp.movq, opTmp(Rbx, ctx2), Rbx, writer);
                printCode(AsmOp.salq, "$3", Rbx,writer);
//                printCode(AsmOp.movq, "$8", Rax, writer);
//                printCode(AsmOp.mulq, Rbx, "", writer);
                printCode(AsmOp.popq, Rcx, "", writer);
                printCode(AsmOp.addq, Rax, Rcx, writer);
                printCode(AsmOp.pushq, Rcx, "", writer);
                break;
            case StarIr:
                ctx1 = new ValueContext();
                ctx2 = new ValueContext();
                printValue(quadIr.arg1, ctx1, writer);
                printCode(AsmOp.popq, Rax, "", writer);
                printCode(AsmOp.movq, addr(Rax, 0), Rax, writer);
                printValue(quadIr.res, ctx2, writer);
                if(ctx2.vtype == Variable.VType.Tmp || ctx2.vtype == Variable.VType.Lit)
                    printCode(AsmOp.pushq, Rbx, "", writer);
                else
                {
                    printCode(AsmOp.popq, Rbx, "", writer);
                    printCode(AsmOp.movq, Rax, addr(Rbx, 0), writer);
                }
                break;
            case GoToIr:
                ctx1 = new ValueContext();
                printValue(quadIr.arg1, ctx1, writer);
                printCode(AsmOp.jmpq, "block" + ctx1.blockIr.blockId, "", writer);
                break;
            case AddrIr:
                ctx1 = new ValueContext();
                ctx2 = new ValueContext();
                printValue(quadIr.arg1, ctx1, writer);
                printValue(quadIr.res, ctx2, writer);
                if(ctx1.vtype != Variable.VType.Tmp)
                {
                    printCode(AsmOp.popq, Rax, "", writer);
                    printCode(AsmOp.popq, Rbx, "", writer);
                    printCode(AsmOp.movq, Rbx, addr(Rax, 0), writer);
                }
                break;
            case LastRetIr:
                printCode(AsmOp.leave, "", "", writer);
                printCode(AsmOp.ret, "", "" , writer);
                break;
        }
        beforeQuad = quadIr;
    }

    public String opTmp(String base, ValueContext valueContext)
    {
        if(valueContext.vtype == Variable.VType.Tmp || valueContext.vtype == Variable.VType.Lit)
            return base;
        return addr(base, 0);
    }

    public String addr(String base, int offset)
    {
        return offset + "(" + base + ")";
    }

    public void printValue(ValueIr valueIr, ValueContext ctx, OutputStreamWriter writer) throws IOException
    {
        if(valueIr == null)
            return;
        switch(valueIr.type)
        {
            case Var: printVariable((Variable) valueIr.value, ctx, writer);break;
            case Block:ctx.blockIr = (BlockIr) valueIr.value;break;
            case Func:ctx.funcIr = (FuncIr) valueIr.value;break;
            case Int:ctx.number = (int)valueIr.value;break;
        }
    }

    public void printVariable(Variable variable, ValueContext ctx, OutputStreamWriter writer) throws IOException
    {
        if(variable == null)
            return;
        switch(variable.vtype)
        {
            case Var: case Const: printVarAddr(variable, writer);break;
            case Tmp: ctx.vtype = Variable.VType.Tmp;break;
            case Lit:
                BasicLit basicLit = (BasicLit) variable.type;
                switch(basicLit.kind)
                {
                    case ImaginaryInt:case RuneInt:case HexInt:case DecimalInt:
                    case OctalInt: case BinaryInt:
                        printCode(AsmOp.pushq, "$" + basicLit.value, "", writer);
                        ctx.vtype = variable.vtype;
                        break;
                }
                break;
        }
    }

    public void getVariable(ValueIr valueIr, ValueContext ctx, String reg, OutputStreamWriter writer) throws IOException
    {
        if(valueIr == null)
            return;
        if(valueIr.value == null)
            return;
        Variable variable = (Variable) valueIr.value;
        switch(variable.vtype)
        {
            case Var: case Const:
            if(variable.isGlobal)
                println(writer, 2, "leaq\t" + variable.globalId + "(" + Rip + "),\t" + reg);
            else
                printCode(AsmOp.leaq, variable.offset + "(" + Rbp + ")", reg, writer);
                break;
            case Tmp: ctx.vtype = Variable.VType.Tmp;break;
            case Lit:
                BasicLit basicLit = (BasicLit) variable.type;
                switch(basicLit.kind)
                {
                    case ImaginaryInt:case RuneInt:case HexInt:case DecimalInt:
                    case OctalInt: case BinaryInt:
                    printCode(AsmOp.movq, "$" + basicLit.value, reg, writer);
                    ctx.vtype = variable.vtype;
                    break;
                }
                break;
        }
    }

    public void printVarAddr(Variable variable, OutputStreamWriter writer) throws IOException
    {
        if(variable.isGlobal)
        {
            println(writer, 2, "leaq\t" + variable.globalId + "(" + Rip + "),\t" + Rax);
        }
        else
            printCode(AsmOp.leaq, variable.offset + "(" + Rbp + ")", Rax, writer);
        printCode(AsmOp.pushq, Rax, "", writer);
    }

    public void printFuncLocalVar(Function function, OutputStreamWriter writer) throws IOException
    {
        printCode(AsmOp.pushq, Rbp, "", writer);
        printCode(AsmOp.movq, Rsp, Rbp, writer);
        if(function.localArea != 0)
        {
            printCode(AsmOp.subq, "$" + String.valueOf(-function.localArea), Rsp, writer);
        }
    }

    public void printCode(AsmOp asmOp, String src, String dst, OutputStreamWriter writer) throws IOException
    {
        if(dst.equals(""))
        {
            if(src.equals(""))
                println(writer, 2, asmOp.toString());
            else
                println(writer, 2, asmOp.toString() + "\t" + src);
        }
        else
            println(writer, 2, asmOp.toString() + "\t" + src +", " + dst);
    }

    public void printPackageData(PackageIr packageIr, OutputStreamWriter writer) throws IOException
    {

        for(ValueDecl valueDecl : packageIr.values)
        {
            for(int i=0; i<valueDecl.names.size(); i++)
            {
                Variable variable = (Variable) ((ScopeObject)valueDecl.names.get(i).object).value;
                println(writer, 0, variable.globalId + ":");
                ValueDecl decl = (ValueDecl) ((ScopeObject)valueDecl.names.get(i).object).field;
                Expr value = null;
                if(decl.value.size() != 0)
                {
                    value = decl.value.get(i);
                }
                String type = irGenerator.getKind((Expr) variable.type);
                switch(type)
                {
                    case "bool":
                        if(value.getClass().getName().equals("Id"))
                        {
                            Id id = (Id)value;
                            if(id.object == irGenerator.TrueType)
                                println(writer, 1, ".quad 1");
                            else if(id.object == irGenerator.FalseType)
                                println(writer, 1, ".quad 0");
                        }
                        else
                            println(writer, 1, ".quad 0");
                    case "int": case "int32":
                        if(value.getClass().getName().equals("BasicLit"))
                            println(writer, 1, ".quad " + ((BasicLit)value).value);
                        else
                            println(writer, 1, ".quad 0");
                        break;

                }
            }
        }
    }

    public static void println(OutputStreamWriter writer, int layer, String src) throws IOException
    {
        for(int i=0; i<layer; i++)
            writer.write("\t");
        writer.write(src + "\n");
    }
}
